home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-04-05 | 9.9 KB | 246 lines | [TEXT/MWPR] |
- Debugging With Sherlock
-
-
- This chapter offers tips on how to find and correct bugs more effectively. C programs are
- particularly challenging to debug because the C language does not limit what you can do. This
- Chapter is based on parts of the book Debugging C, with the permission of Robert Ward.
-
-
- Pointer Bugs
-
- The key to learning how to debug C programs is recognizing, understanding and correcting
- pointer bugs. Pointers pervade all of C and they give the C language much of its power and
- flexibility. However, pointers are dangerous, as well as useful.
-
-
- This section addresses the most common situations involving pointer bugs:
-
-
- • Pointer bugs arise from uninitialized pointers, dangling pointers and incorrect use of arguments
- to function.
-
- • Pointer bugs often destroy the computer code. The code destroyed by pointer bugs may have
- been the code you wrote, code comprising run-time functions such as printf(), or operating system
- code.
-
- • To the new C programmer, pointer bugs produce symptoms that look like hardware malfunctions
- or compiler bugs. The experienced C programmer recognizes these same symptoms as clear
- indications of the existence of pointer bugs.
-
-
- Types of Pointer Bugs
-
- Pointer bugs arise from uninitialized pointers, dangling pointers and incorrect use of arguments to
- functions. An uninitialized pointer is simply a pointer variable which is used before it has been
- given a value. For example,
-
-
- error1()
-
- {
-
- char *p;
-
- *p = 'a';
-
- }
-
-
- In this example, p does not point to any specified location at the time that it used to store the
- character 'a'. The location in memory is not specified by the code, and the results are
- unpredictable. Possible symptoms will be discussed later.
-
- The second type of pointer bug is the dangling pointer. A dangling pointer refers to an area of
- memory which is no longer being used for its original purpose. For example,
-
-
- error2()
-
- {
-
- char *p, *malloc();
-
- p = malloc(25);
-
- free(p);
-
- }
-
-
-
- The call to malloc() makes p point to an area of memory containing room for 25 characters. The
- call to free() deallocates the space and causes p to become a dangling pointer. However, there is no
- bug in this code. Any time you deallocate memory you create a dangling pointer—bugs arise from
- using dangling pointers rather than just creating them.
-
-
- Again, the results of using a dangling pointer are unpredictable. They are not specified by the C
- program. In this example, if the memory released by the call to free() is later reallocated, using p
- will probably corrupt the newly allocated memory.
-
-
- A third kind of pointer bug is the mistaken parameter bug. A good example is the following:
-
-
- error3()
-
- {
-
- int i;
-
- scanf("%d", i);
-
- }
-
-
- This will not work as expected. The second argument to scanf() should have been &i, not i. The
- scanf() function expects a pointer to i and gets the value of i instead. Thus, the pointer that scanf()
- expects is incorrect. Once again this creates a hard-to-find bug.
-
-
- This kind of error can corrupt the system stack. This happens because the called routines assume
- the stack has a different structure from the stack that was actually created by the caller. If the called
- program alters any stack variable, it will be altering a part of the stack that may not actually
- correspond to the location of the stack variable.
-
-
- This kind of bug can be eliminated by using function prototyping.
-
- Effects of Pointer Bugs
-
- Pointer bugs can be devastating. Any pointer bug has the potential for destroying any part of
- memory—executable code, library functions such as printf(), the system stack used to keep track
- of procedure calls and returns or even the operating system itself. Exactly which part of memory
- depends on the value the pointer had at the time your program was loaded.
-
-
-
- Symptoms of Pointer Bugs
-
- The symptoms of pointer bugs vary depending on just what kind of code or data area are
- destroyed by the bug. Such symptoms can not be taken at face value. When confronted with
- behavior such as described below, always think first of pointer bugs.
-
-
- Symptom : Your program crashes inside a function which you have written and which you know
- to be debugged.
-
- Cause: A pointer bug has destroyed your carefully debugged function.
-
-
- Symptom: A library function suddenly ceases to work correctly.
-
- Cause: A pointer bug has destroyed the library function instead of your own code.
-
-
- Symptom : A bug is solid, i.e., it manifests itself in the same way when you run the program
- several times. However, the bug goes away when you insert print statements into the code to get
- more information about it.
-
- Cause: Inserting the print statements changed the location of various parts of your code. Before
- the print statements were inserted, the pointer bug destroyed code that was still to be executed.
- After inserting the print statements, the pointer bug destroyed a part of the program which was no
- longer executed after the pointer bug occurred.
-
-
- Symptom: The symptoms of a bug change after you insert print statements.
-
- Cause: Inserting print statements changed the code destroyed by the pointer bug.
-
-
- Symptom: By inserting print statements you can determine that control reaches a particular
- statement but not the statement immediately following.
-
- Cause: A pointer bug has destroyed one or both of the statements.
-
-
- Symptom: Your program calls a function, but control never reaches the function.
-
- Cause: A pointer bug has destroyed either the system stack or the function itself.
-
- Symptom: Control never returns to the caller of a function after the called function returns.
-
- Cause: A pointer bug has destroyed either the run-time stack or the function itself.
-
-
- Symptom: Your program sometimes works and sometimes crashes.
-
- Cause: An uninitialized variable is destroying random parts of your program depending on the
- contents of certain memory locations before your program was invoked. The pointer bug is
- destroying different memory locations each time your program is being run. Sometimes the
- destroyed locations do not affect your program and sometimes they do.
-
-
- Symptom: Your program always works once, but fails the second time it is run.
-
- Cause: Your program contains an uninitialized variable. The first time your program runs the
- variable is initialized in a uniform way which does not cause any apparent harm. The second time
- your program runs, the variable has a new initial value which depends on the program’s first run.
- This second initial value causes the symptoms the second time the program is run.
-
-
-
- Using Sherlock to Find Bugs
-
- Using Sherlock to locate bugs is a two-step process: 1) make the bug happen consistently and
-
- 2) make a plausible guess about its cause.
-
-
- The first step in finding the cause of any bug is to make the bug “stand still” so that you can study
- it. You can do this in the following ways:
-
-
- 1. Fill memory with a constant value before you run your program. This will insure that
- uninitialized pointers get the same (though probably still incorrect) value from run to run. If filling
- memory with a constant value makes the symptoms of your bug go away, you can be reasonably
- sure that some kind of initialization problem is causing the bug.
-
-
- 2. Eliminate the effects (including symptoms) of most dangling pointers by disabling any routine
- which frees a dynamically allocated data structure. You can do that by providing an alternative
- deallocation routine which is a dummy routine. When you do that, dangling pointers no longer
- dangle, i.e., they no longer point to deallocated memory. If disabling a routine such as free()
- makes the symptoms of your bug go away, you can be reasonably sure that a dangling pointer is at
- hand.
-
-
-
- 3. Keep precise records about how you invoked your program. An easy way to do this is by
- invoking the program from a batch file, also known as a submit file or shell file. That way the
- batch file serves as a record for exactly what you have done. A tip: when you change the
- arguments to your program, comment out the old line and save it in the batch file as a record of the
- your previous runs. This is especially handy when using numerous Sherlock tracepoints to
- change the behavior of your program during testing.
-
-
- 4. Keep your program unchanged. Since pointer bugs destroy code, even the smallest change in
- your program, or even a change in the order in which functions are linked together, may cause the
- symptoms of pointer bugs to change or even go away. Sherlock is a huge help here. Enabling or
- disabling different tracepoints does not change the location of any code in your program. You can
- run test after test on your program, getting very different information each time, and the symptoms
- of pointer bugs will not change.
-
-
- The second step in finding a bug is to make a guess about what is causing it. If any of the
- symptoms mentioned above appear, you probably should assume that a pointer bug is causing
- your problems. Use Sherlock to look for bad pointers by tracing the parameters passed to all your
- functions.
-
-
- If a supposed pointer has a small negative number as its value (.e.g., FFFFE hex), you can be
- sure that the pointer has already been corrupted. You may also notice that a pointer does not have
- a reasonable value in some other way. Now ask yourself, “Which routines could have passed that
- pointer on to the called routine?” Rerun your program with different tracepoints enabled which
- will trace the likely culprits. Once you find the source of a bad pointer, ask whether the code
- which created the bad pointer was ultimately at fault or whether some other error resulted in the
- bad pointer as a by-product.
-
-
- You will find that patterns jump out at you as you look at traces and dumps produced by Sherlock.
- When you get a hint of something in a trace being not quite right, you can immediately start a new
- trace to zero in on those parts of the program that are related to what caught your attention. By
- varying your traces to home in on suspects, you are eliminating vast amounts of extraneous
- information in the debugging output.
-
-